#include <keyboard.h>
#include <pio.h>
#include <std.h>
#include <error.h>
#include <type.h>
#include <dint.h>

/*Minix:*/

#define PORT_B          0x61  	//8255 port kbd

/* Standard and AT keyboard.  (PS/2 MCA implies AT throughout.) */
#define KEYBD		0x60	/* I/O port for keyboard data */

/* AT keyboard. */
#define KB_COMMAND	0x64	/* I/O port for commands on AT */
#define KB_STATUS	0x64	/* I/O port for status on AT */
#define KB_ACK		0xFA	/* keyboard ack response */
#define KB_OUT_FULL	0x01	/* status bit set when keypress char pending */
#define KB_IN_FULL	0x02	/* status bit set when not ready to receive */
#define LED_CODE	0xED	/* command to keyboard to set LEDs */
#define MAX_KB_ACK_RETRIES 100000	/* max #times to wait for kb ack */
#define MAX_KB_BUSY_RETRIES 100000	/* max #times to loop while kb busy */
#define KBIT		0x80	/* bit used to ack characters to keyboard */

/* Miscellaneous. */
#define ESC_SCAN	0x01	/* Reboot key when panicking */
#define SLASH_SCAN	0x35	/* to recognize numeric slash */
#define RSHIFT_SCAN	0x36	/* to distinguish left and right shift */
#define HOME_SCAN	0x47	/* first key on the numeric keypad */
#define INS_SCAN	0x52	/* INS for use in CTRL-ALT-INS reboot */
#define DEL_SCAN	0x53	/* DEL for use in CTRL-ALT-DEL reboot */
#define CONSOLE		   0	/* line number for console */
#define MEMCHECK_ADR   0x472	/* address to stop memory check after reboot */
#define MEMCHECK_MAG  0x1234	/* magic number to stop memory check */

#define KB_IN_BYTES	  32	/* size of keyboard input buffer */

int kb_wait()
{
/* Wait until the controller is ready; return zero if this times out. */

  int retries, status;

  retries = MAX_KB_BUSY_RETRIES + 1;	/* wait until not busy */
  while (--retries != 0
		&& (status = inb(KB_STATUS)) & (KB_IN_FULL|KB_OUT_FULL)) {
	if (status & KB_OUT_FULL) (void) inb(KEYBD);	/* discard */
  }
  return(retries);		/* nonzero if ready */
}

static int kb_ack()
{
/* Wait until kbd acknowledges last command; return zero if this times out. */

  int retries;

  retries = MAX_KB_ACK_RETRIES + 1;
  while (--retries != 0 && inb(KEYBD) != KB_ACK)
	;			/* wait for ack */
  return(retries);		/* nonzero if ack received */
}

static unsigned char scan_keyboard()
{
/* Fetch the character from the keyboard hardware and acknowledge it. */

  int code;
  int val;

  code = inb(KEYBD);		/* get the scan code for the key struck */
  val = inb(PORT_B);		/* strobe the keyboard to ack the char */
  outb(PORT_B, val | KBIT);	/* strobe the bit high */
  outb(PORT_B, val);		/* now strobe it low */
  return code;
}

#define	META_ALT	0x01	/* Alt is pressed */
#define	META_CTRL	0x02	/* Ctrl is pressed */
#define	META_SHIFT	0x04	/* Shift is pressed */
#define	META_ANY	(KBD_META_ALT | KBD_META_CTRL | KBD_META_SHIFT)
#define	META_CAPS	0x10	/* CapsLock is on */
#define	META_NUM	0x20	/* NumLock is on */
#define	META_SCRL	0x40	/* ScrollLock is on */

/* function keys: */
#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)
/* cursor keys */
#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
/* print screen/sys rq and pause/break */
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
/* these return a value but they could also act as additional bucky keys */
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

#define	RAW1_LEFT_CTRL		0x1D
#define	RAW1_RIGHT_CTRL		0x1D	/* same as left */
#define	RAW1_LEFT_SHIFT		0x2A
#define	RAW1_RIGHT_SHIFT	0x36
#define	RAW1_LEFT_ALT		0x38
#define	RAW1_RIGHT_ALT		0x38	/* same as left */
#define	RAW1_CAPS_LOCK		0x3A
#define	RAW1_F1			0x3B
#define	RAW1_F2			0x3C
#define	RAW1_F3			0x3D
#define	RAW1_F4			0x3E
#define	RAW1_F5			0x3F
#define	RAW1_F6			0x40
#define	RAW1_F7			0x41
#define	RAW1_F8			0x42
#define	RAW1_F9			0x43
#define	RAW1_F10		0x44
#define	RAW1_NUM_LOCK		0x45
#define	RAW1_SCROLL_LOCK	0x46
#define	RAW1_F11		0x57
#define	RAW1_F12		0x58

#define	KBD_BUF_SIZE		64
#define KBD_TIMEOUT 500000

static int ibuf_empty();
static int obuf_empty();


u32 write_kbd(u32 adr, u32 data)
{
	kb_wait();
	outb(adr,data);
	kb_ack();
	return SUCCESS;
}

int set_kbd_leds(unsigned char status)
{
	spin_lock kbd_l;
	acquire_spin_lock(&kbd_l);
	write_kbd(K_RDWR,K_CMD_LEDS);
	write_kbd(K_RDWR,status);
	release_spin_lock(&kbd_l);
	return SUCCESS;
}

static const unsigned char set1_map_alt[]=
{
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0x22,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LFT,'5',KEY_RT,'+',KEY_END,
	KEY_DN,KEY_PGDN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};

static const unsigned char set1_map_ctrl[]=
{
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0xff,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LFT,'5',KEY_RT,'+',KEY_END,
	KEY_DN,KEY_PGDN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};

static const unsigned char set1_map_shift[]=
{
	0,0x1b,'!','@','#','$','%','^','&','*','(',')','_','+',
	'\b','\t','Q','W','E','R','T','Y','U','I','O','P','{',
	'}','\n',0,'A','S','D','F','G','H','J','K','L',':',0x22,
	0,0,0,'Z','X','C','V','B','N','M',0X3C,0X3E,'?',0,	0,
	0,' ',0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LFT,'5',KEY_RT,'+',KEY_END,
	KEY_DN,KEY_PGDN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};

static const unsigned char set1_map[] =
{
	0,0x1B,'1','2','3','4','5','6',
	'7','8','9','0','-','=','\b','\t',
	'q','w','e','r','t','y','u','i',
	'o','p','[',']','\n',0,'a','s',
	'd','f','g','h','j','k','l',';',
	'\'','`',0,'\\','z','x','c','v',
	'b','n','m',',','.','/',0,0,
	0,' ',0,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,
	KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,0,0,KEY_HOME,
	KEY_UP,KEY_PGUP,'-',KEY_LFT,'5',KEY_RT,'+',KEY_END,
	KEY_DN,KEY_PGDN,KEY_INS,KEY_DEL,0,0,0,KEY_F11,
	KEY_F12
};
	
/*****************************************************************************
*****************************************************************************/
static unsigned short kbd_status;

int set_leds(unsigned char stat)
{
	spin_lock kbd_l;
	acquire_spin_lock(&kbd_l);
	unsigned char temp;
	temp=0;
	if((stat & META_SCRL)==META_SCRL)
		temp |= 1;
	if((stat & META_NUM)==META_NUM)
		temp |= 2;
	if((stat & META_CAPS)==META_CAPS)
		temp |= 4;
	write_kbd(K_RDWR, 0xED);
	write_kbd(K_RDWR, temp);
	release_spin_lock(&kbd_l);
	return SUCCESS;
}

int togglenumlock()
{
	kbd_status ^= META_NUM;
	set_leds(kbd_status);
	return 0;
}

int togglescrlock()
{
	kbd_status ^= META_SCRL;
	set_leds(kbd_status);
	return 0;
}

int togglecaplock()
{
	kbd_status ^= META_CAPS;
	set_leds(kbd_status);
	return 0;
}

static unsigned convert(unsigned key)
{
	static unsigned short saw_break_code;
	unsigned short temp;
	if(key >= 0x80)
	{
		saw_break_code = 1;
		key &= 0x7F;
	}
	if(saw_break_code)
	{
		if(key == RAW1_LEFT_ALT || key == RAW1_RIGHT_ALT)
			kbd_status &= ~META_ALT;
		else if(key == RAW1_LEFT_CTRL || key == RAW1_RIGHT_CTRL)
			kbd_status &= ~META_CTRL;
		else if(key == RAW1_LEFT_SHIFT || key == RAW1_RIGHT_SHIFT)
			kbd_status &= ~META_SHIFT;
		saw_break_code = 0;
		return 0;
	}
	if(key == RAW1_LEFT_ALT || key == RAW1_RIGHT_ALT)
	{
		kbd_status |= META_ALT;
		return 0;
	}
	if(key == RAW1_LEFT_CTRL || key == RAW1_RIGHT_CTRL)
	{
		kbd_status |= META_CTRL;
		return 0;
	}
	if(key == RAW1_LEFT_SHIFT || key == RAW1_RIGHT_SHIFT)
	{
		kbd_status |= META_SHIFT;
		return 0;
	}
	if(key == RAW1_SCROLL_LOCK)
	{
		kbd_status ^= META_SCRL;
		set_leds(kbd_status);
		return 0;
	}
	if(key == RAW1_NUM_LOCK)
	{
		kbd_status ^= META_NUM;
		set_leds(kbd_status);
		return 0;
	}
	if(key == RAW1_CAPS_LOCK)
	{
		kbd_status ^= META_CAPS;
		set_leds(kbd_status);
		return 0;
	}
	if(key >= sizeof(set1_map) / sizeof(set1_map[0]))	{
		printdev("\n[%x]",key);
		return 0;
	}
	if((kbd_status&META_SHIFT)==META_SHIFT || (kbd_status&META_CAPS)==META_CAPS)	{
		temp=set1_map_shift[key];
	}
	else if((kbd_status&META_CTRL)==META_CTRL)	{
		temp=set1_map_ctrl[key];
	}
	else if((kbd_status&META_ALT)==META_ALT)	{
		temp=set1_map_alt[key];
	}
	else if(key==RAW1_F1)	{
		start_mono();
		printf("\nPanic system halt: Real mode press any key to resume...");
		bioscall(0,0,0,0,0,0,0x16);
		printf("\nResume...");
		release_mono();
	}
	else	{
		temp = set1_map[key];
	}
	if(temp == 0)
		return temp;
	if((kbd_status & META_CTRL) && (kbd_status & META_ALT) &&
		(temp == KEY_DEL))
	{
	}
	return temp;
}
/*****************************************************************************
*****************************************************************************/
static int key_queue[KBD_BUF_SIZE];
static unsigned char bal=0;

static void shift_queue_right()
{
	char i;
	for(i=KBD_BUF_SIZE;i>0;i--)
	{
		key_queue[i]=key_queue[i-1];
	}
}

static void shift_queue_left()
{
	unsigned char i;
	for(i=0;i<KBD_BUF_SIZE;i++)
	{
		key_queue[i]=key_queue[i+1];
	}
}

static void queue_key(int key)
{
	key_queue[bal]=key;
	if(bal<KBD_BUF_SIZE)
	{
		bal++;
	}
	else
	{
		shift_queue_right();
	}
}

int get_key()
{
	int ret_key;
	spin_lock getkey;
	acquire_spin_lock(&getkey);
	if(bal==0)
	{
		ret_key=0;
	}
	else
	{
		ret_key=key_queue[0];
		shift_queue_left();
		bal--;
	}
	release_spin_lock(&getkey);
	return ret_key;
}

#define PIC1_RESET_PORT 0x20
#define PIC2_RESET_PORT 0xa0
#define PIC_RESET_COMMAND 0x20

static int obuf_empty()
{
	unsigned long timeout=KBD_TIMEOUT;
	unsigned char temp;
	while((--timeout)>0)	{
		temp=inb(K_CMD);
		if((temp&K_OBUF_FUL)!=0)	{
			inb(K_RDWR);
		}
		else	{
			return NULL;
		}
	}
	return temp;
}

static int ibuf_empty()
{
	unsigned long timeout=KBD_TIMEOUT;
	unsigned char temp;
	while((--timeout)>0)	{
		temp=inb(K_CMD);
		if((temp&K_IBUF_FUL)==0)	{
			return NULL;
		}
	}
	return temp;
}


/* Keyboard IRQ hook--------------->*/
void keyboard(void)
{
	unsigned key, i;
/* get scancode from port 0x60 */
	key = scan_keyboard();
		i = convert(key);
		queue_key(i);
}	

int empty_8042()
{
	unsigned char temp;
	spin_lock e;
	acquire_spin_lock(&e);
	do	{
		temp = inb(K_CMD);
		if((temp & K_OBUF_FUL) != 0)	{
			(void)inb(K_RDWR);
			continue;
		}
	} 
	while((temp & K_IBUF_FUL) != 0);
	release_spin_lock(&e);
	return SUCCESS;
}

int kbd_set_typematic(unsigned char delay,unsigned char rate)
{
	unsigned char res=0;
	res|=0x80;
	res|=(delay*32);
	res|=rate;
	write_kbd(K_RDWR,K_CMD_TYPEMATIC);
	write_kbd(K_RDWR,0);
	return SUCCESS;
}

void reboot(void)
{
	unsigned temp;
	end_all_processes();
	empty_8042();
	outb(K_RDWR,KC_CMD_PULSE);
	mdelay(10);
	outb(K_CMD, K_RESET);
	while(1);
}

#define SCRLLOCK	0x1
#define NUMLOCK		0x2
#define CAPSLOCK	0x4


#define DIAG 0xee
#define DISABLE_KB 0xf5
#define ENABLE_KB 0xf4


int init_kbd()
{
	printdev("\nStarting keyboard");
	request_irq(1,(void *)keyboard);
	enable_irq(1);
	bal=NULL;
	kbd_status=NULL;
	set_kbd_leds(NULL);
	empty_8042();
	kbd_set_typematic(0,0);
	return SUCCESS;
}

int deinit_kbd()
{
	release_irq(1,(void *)keyboard);
	disable_irq(1);
	set_kbd_leds(NULL);
	empty_8042();
	return SUCCESS;
}

int reset_kbd()
{
	write_kbd(0x60,0xff);
	return SUCCESS;
}

u32 read_kbdc(u32 endp)
{
	return (u32)scan_keyboard();
}

kdrv kbd={
	.name="keyboard",
	.init=init_kbd,
	.reset=reset_kbd,
	.deinit=deinit_kbd,
	.select_subdevice=NULL,
	.read_char=read_kbdc,
	.read_block=NULL,
	.write_char=write_kbd,
	.write_block=NULL,
};


